wkd2ev


type challange <Easy>

타입 챌린지 easy를 풀어봅시다... https://github.com/type-challenges/type-challenges?tab=readme-ov-file

Pick

// Q interface Todo { title: string description: string completed: boolean } type TodoPreview = MyPick<Todo, 'title' | 'completed'> const todo: TodoPreview = { title: 'Clean room', completed: false, } // A type MyPick<T, K extends keyof T> = { [key in K]: T[key]; }

타입스크립트에서 기본적으로 제공해주는 유틸리티 타입인 Pick이다. 제너릭의 두번째 인자로 뽑고싶은 속성만 추출한다. K extends keyof T 는 K가 T 제너릭 타입이 가지고 있는 속성들의 서브타입임을 명시해준다.

Readonly

// Q interface Todo { title: string description: string } const todo: MyReadonly<Todo> = { title: "Hey", description: "foobar" } todo.title = "Hello" // Error: cannot reassign a readonly property todo.description = "barFoo" // Error: cannot reassign a readonly property // A type MyReadonly<T> = { readonly [key in keyof T]: T[key]; }

모든 속성을 읽기 속성으로 만들어야 한다.

FirstofArray

// Q type arr1 = ['a', 'b', 'c'] type arr2 = [3, 2, 1] type head1 = First<arr1> // expected to be 'a' type head2 = First<arr2> // expected to be 3 type head3 = First<[]> // expected to be never // A type First<T extends any[]> = T extends [] ? never : T[0];

원소[0] 을 반환하면 될 것 같지만 빈 배열일 경우에도 생각해야 한다.

조건부 연산을 사용하여 제너릭 T가 빈배열의 서브타입일 경우 never를 반환하고 아니라면 0번째 인덱스를 반환해준다.

Length of Tuple

type tesla = ['tesla', 'model 3', 'model X', 'model Y'] type spaceX = ['FALCON 9', 'FALCON HEAVY', 'DRAGON', 'STARSHIP', 'HUMAN SPACEFLIGHT'] type teslaLength = Length<tesla> // expected 4 type spaceXLength = Length<spaceX> // expected 5 type Length<T extends any[]> = T["length"];

length 프로퍼티를 이용하여 반환하면 된다. 다만 as const로 타입 단언이 선언되었을 경우 다음과 같이 작성해줘야 한다.

const tesla = ['tesla', 'model 3', 'model X', 'model Y'] as const; type TeslaLen = Length<typeof tesla>; <---- ???? type Length<T extends readonly any[]> = T["length"];

Exclude

// Q type Result = MyExclude<'a' | 'b' | 'c', 'a'> // 'b' | 'c' // A type MyExclude<T, U> = T extends U ? never : T;

첫 제너릭 속성에는 유니온 타입을 두번쨰 제너릭 인자로 제외하고 싶은 타입을 넣어 제외시키는 타입이다.

T가 U의 서브타입이라면 never를 반환

'a' | 'b' | 'c' 중 'a'는 두번째 제너릭으로 들어온 타입 'a'의 서브타입이므로 never를 반환하고 아님 T를 반환한다.

Awaited

// Q type X = Promise<string> type Y = Promise<{ field: number }> // A type MyAwaited<T> = T extends Promise<infer R> ? R : never;

프로미스의 타입을 뽑아내는 문제이다. infer 키워드를 사용하여 타입을 추출할 수 있다.

하지만 다음과 같이 프로미스가 프로미스를 반환한다면 프로미스가 프로미스를 반환하고 재귀적으로 프로미스를 반환해줄 경우 재귀적으로 타입을 작성해준다.

type Z = MyAwaited<Promise<string | number>> type Z1 = MyAwaited<Promise<Promise<string | boolean>>> type MyAwaited<T extends Promise<unknown>> = T extends Promise<infer R> ? R extends Promise<unknown> // R이 프로미스의의 서브타입이라면 ? MyAwaited<R> // 재귀적으로 R을 던짐으로 호출 : R // R이 프로미스의 서브타입이 아니라면 R을 반환 : never; // 모든 케이스에 대응하지 않은 경우 never 반환

IF

type A = If<true, 'a', 'b'> // expected to be 'a' type B = If<false, 'a', 'b'> // expected to be 'b' type If<C extends boolean, T, F> = C extends true ? T : F;

Concat

// Q type Result1 = MyConcat<[], []> type Result2 = MyConcat<[], [1]> type Result3 = MyConcat<[1, 2], [3, 4]> type Result4 = MyConcat<['1', 2, '3'], [false, boolean, '4']> // A type MyConcat<T extends any[], U extends any[]> = [...T, ...U];

스프레드 연산자로 합쳐주면 된다.

Equal

해당 타입은 문제에 없지만 후에 나올 Include 타입 문제에 쓰이기 때문에 한번 알아본다. 타입이 동등한지를 비교하는 타입이다.

interface Person { name: string; age: number; } interface Person2 { name: string; age: number; } interface OtherType { name:string; money: number; } type Result1 = Equals<Person, Person>; // expected true type Result2 = Equals<Person, Person2>; // expected true type Result3 = Equals<Person2, OtherType>; // expected false type Equals<X, Y> = (<T>() => T extends X ? "A" : "B") extends (<T>() => T extends Y ? "A" : "B") ? true : false;

꽤나 신기한 타입이다. 제너릭 T를 받아 해당 타입이 Equals타입의 두 제너릭 타입의 서브 타입인지를 비교하고 또 서로 비교하는 과정을 통해 true, false를 반환한다.

Include

배열 원소에 타입이 포함되어 있는지를 묻는 문제이다.

// Q type isPillarMen = Includes<['Kars', 'Esidisi', 'Wamuu', 'Santana'], 'Dio'> // expected to be `false` // A type Equal<X, Y> = (<T>() => T extends X ? "A" : "B") extends (<T>() => T extends Y ? "A" : "B") ? true : false; type Includes<T extends readonly any[], U> = T extends [infer First, ...infer Rest] // 제너릭 T 타입은 infer로 타입을 추출 처음 원소와 나머지 원소로 분리한다. ? Equal<First, U> extends true // Equal 타입으로 첫번째 원소와 U를 비교를 하고 true의 서브타입인지 판별한다. ? true // 조건이 맞다면 true를 반환 : Includes<Rest, U> // 1에서 판별한 조건이 아니라면 재귀적으로 나머지 배열 원소의 타입들을 넣어주어 재귀적으로 호출한다. : false; // 모든 케이스에 대응 안하는 경우 false를 반환

Push

// Q type Result = Push<[1, 2], '3'> // [1, 2, '3'] // A type Push<T extends any[], U> = [...T, U];

Unshift

// Q type Result = Unshift<[1, 2], 0> // [0, 1, 2,] // A type Unshift<T extends any[], U> = [U, ...T];

Parameters

함수의 파라미터를 추출하는 문제다.

// Q const foo = (arg1: string, arg2: number): void => {} type FunctionParamsType = MyParameters<typeof foo> // [arg1: string, arg2: number] // A type MyParameters<T extends (...args: any[]) => any> = T extends (...args: infer R) => any ? R : never;

함수의 파라미터 타입을 infer로 추론한다. 제너릭으로 받은 T타입이 함수 형태가 아니라면 never를 반환한다.

최종 수정일: 2022-11-13